#include "webserver.h"

WebServer::WebServer(
    int port, int trigMode, int timeoutMS, bool OptLinger,
    int sqlPort, const char *sqlUser, const char *sqlPwd,
    const char *dbName, int connPoolNum, int threadNum,
    bool openLog, int logLevel, int logQueSize) : port_(port), openLinger_(OptLinger), timeoutMS_(timeoutMS), isClose_(false),
                                                  timer_(new HeapTimer()), threadpool_(new ThreadPool(threadNum)), epoller_(new Epoll())
{
    if (openLog)
    {
        Log::Instance()->init(logLevel, "log", ".log", logQueSize);
        if (isClose_)
        {
            LOG_ERROR("========== Server init error!==========");
        }
        else
        {
            LOG_INFO("========== Server init ==========");
            LOG_INFO("Port:%d, OpenLinger: %s", port_, OptLinger ? "true" : "false");
            LOG_INFO("Listen Mode: %s, OpenConn Mode: %s",
                     (listenEvent_ & EPOLLET ? "ET" : "LT"),
                     (connEvent_ & EPOLLET ? "ET" : "LT"));
            LOG_INFO("LogSys level: %d", logLevel);
            LOG_INFO("srcDir: %s", HttpConn::srcDir);
            LOG_INFO("SqlConnPool num: %d, ThreadPool num: %d", connPoolNum, threadNum);
        }
    }
    // threadpool_->start();
    srcDir_ = getcwd(nullptr, 256);
    assert(srcDir_ != nullptr);
    strncat(srcDir_, "/resources/", 16);
    HttpConn::userCount = 0;
    HttpConn::srcDir = srcDir_;
    SqlConnPool::Instance()->init("localhost", sqlPort, sqlUser, sqlPwd, dbName, connPoolNum);
    InitEventMode_(trigMode);

    if (!InitSocket_())
    {
        isClose_ = true;
    }

    
}

WebServer::~WebServer()
{
    close(listenFd_);
    isClose_ = true;
    free(srcDir_);
    SqlConnPool::Instance()->Close();
}

bool WebServer::InitSocket_()
{
    int listensock = Sock::Socket();
    listenFd_ = listensock;
    Sock::Bind(listenFd_, port_);
    Sock::Listen(listenFd_);
    struct linger Linger;
    if (openLinger_)
    {
        Linger.l_linger = 1;
        Linger.l_onoff = 1;
    }
    setsockopt(listenFd_, SOL_SOCKET, SO_LINGER, &Linger, sizeof(Linger));
    int ret = epoller_->AddFd(listenFd_, listenEvent_ | EPOLLIN);
    if (!ret)
    {
        LOG_ERROR("epoll add error");
        close(listenFd_);
        exit(5);
    }
    SetFdNonblock(listenFd_);
    return true;
}

int WebServer::SetFdNonblock(int fd)
{
    assert(fd >= 0);
    return fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
}

void WebServer::InitEventMode_(int Mode)
{
    switch (Mode)
    {
    case 0:
        break;
    case 1:
        connEvent_ |= EPOLLET;
        break;
    case 2:
        listenEvent_ |= EPOLLET;
        break;
    case 3:
        listenEvent_ |= EPOLLET;
        connEvent_ |= EPOLLET;
        break;
    default:
        listenEvent_ |= EPOLLET;
        connEvent_ |= EPOLLET;
        break;
    }
}

void WebServer::start()
{
    int timeMs = -1;
    if (!isClose_)
    {
        LOG_INFO("==============Server Start==============");
    }
    while (!isClose_)
    {
        if (timeoutMS_ > 0)
        {
            timeoutMS_ = timer_->GetNextTick();
        }

        int eventCnt = epoller_->Wait(timeMs);

        for (int i = 0; i < eventCnt; i++)
        {
            int fd = epoller_->GetEventFd(i);
            int events = epoller_->GetEvents(i);

            if (fd == listenFd_)
            {
                DealListen_();
            }
            else if (events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR))
            {
                assert(users_.count(fd) > 0);
                users_[fd].Close();
            }
            else if (events & EPOLLIN)
            {
                DealRead_(&users_[fd]);
            }
            else if (events & EPOLLOUT)
            {
                DealWrite_(&users_[fd]);
            }
            else
            {
                LOG_ERROR("Unexpected event");
            }
        }
    }
}

void WebServer::DealListen_()
{
    struct sockaddr_in addr;
    do
    {

        int fd = Sock::Accept(listenFd_, &addr);
        if (fd < 0)
            break;
        else if (HttpConn::userCount >= MAX_FD)
        {
            SendError_(fd, "Server busy!");
            LOG_WARN("Clients is full!");
            break;
        }
        AddClient_(fd, addr);
    } while (listenEvent_ & EPOLLET);
    return;
}

void WebServer::AddClient_(int fd, struct sockaddr_in addr)
{
    assert(fd >= 0);
    users_[fd].init(fd, addr);
    if (timeoutMS_ > 0)
        timer_->add(fd, timeoutMS_, std::bind(&WebServer::CloseConn_, this, &users_[fd]));
    epoller_->AddFd(fd, connEvent_ | EPOLLET);
    SetFdNonblock(fd);
    return;
}

void WebServer::ExtentTime_(HttpConn *conn)
{
    assert(conn != nullptr);
    if (timeoutMS_ > 0)
        timer_->adjust(conn->GetFd(), timeoutMS_);
}

void WebServer::DealRead_(HttpConn *client)
{
    assert(client);
    ExtentTime_(client);
    threadpool_->AddTask(std::bind(&WebServer::OnRead_, this, client));
}

void WebServer::DealWrite_(HttpConn *client)
{
    assert(client);
    ExtentTime_(client);
    threadpool_->AddTask(std::bind(&WebServer::OnWrite_, this, client));
}

void WebServer::OnRead_(HttpConn *conn)
{
    assert(conn != nullptr);
    int error = -1;
    ssize_t n = conn->read(&error);
    if (n <= 0 && error != EAGAIN)
    {
        CloseConn_(conn);
        return;
    }
    OnProcess(conn);
}

void WebServer::OnWrite_(HttpConn *conn)
{
    assert(conn != nullptr);
    int error = -1;
    conn->write(&error);
    if (conn->ToWriteBytes() == 0)
    {
        if (conn->IsKeepAlive())
        {
            OnProcess(conn);
            return;
        }
    }
    else
    {
        if (errno == EAGAIN)
        {
            epoller_->ModFd(conn->GetFd(), connEvent_ | EPOLLOUT);
            return;
        }
    }
    CloseConn_(conn);
}

void WebServer::OnProcess(HttpConn *conn)
{
    if (conn->process())
    {
        epoller_->ModFd(conn->GetFd(), connEvent_ | EPOLLOUT);
    }
    else
    {
        epoller_->ModFd(conn->GetFd(), connEvent_ | EPOLLIN);
    }
}

void WebServer::CloseConn_(HttpConn *conn)
{
    assert(conn);
    LOG_INFO("Client[%d] quit!", conn->GetFd());
    epoller_->DelFd(conn->GetFd(), 0);
    conn->Close();
}

void WebServer::SendError_(int fd, const char *info)
{
    int ret = send(fd, info, sizeof(info), 0);
    if (ret < 0)
    {
        LOG_WARN("send error to client[%d] error!", fd);
    }
    close(fd);
}
